/*
 * MegaMek - Copyright (C) 2003,2004,2005 Ben Mazur (bmazur@sev.org)
 *
 *  This program is free software; you can redistribute it and/or modify it
 *  under the terms of the GNU General Public License as published by the Free
 *  Software Foundation; either version 2 of the License, or (at your option)
 *  any later version.
 *
 *  This program is distributed in the hope that it will be useful, but
 *  WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 *  or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
 *  for more details.
 */

package megamek.client.bot;

import java.util.Enumeration;
import java.util.Iterator;

import megamek.common.BattleArmor;
import megamek.common.Compute;
import megamek.common.Coords;
import megamek.common.Entity;
import megamek.common.GameTurn;
import megamek.common.IGame;
import megamek.common.IHex;
import megamek.common.INarcPod;
import megamek.common.Infantry;
import megamek.common.Mech;
import megamek.common.Mounted;
import megamek.common.Protomech;
import megamek.common.Tank;
import megamek.common.TargetRoll;
import megamek.common.Terrains;
import megamek.common.ToHitData;
import megamek.common.actions.BrushOffAttackAction;
import megamek.common.actions.ClubAttackAction;
import megamek.common.actions.KickAttackAction;
import megamek.common.actions.PunchAttackAction;
import megamek.common.actions.PushAttackAction;

public final class PhysicalCalculator
{
	private PhysicalCalculator()
	{
		super();
		// should never call this
	}

	static PhysicalOption calculatePhysicalTurn(TestBot bot)
	{

		final GameTurn myTurn = bot.getMyTurn();
		int entNum = bot.game.getFirstEntityNum(myTurn);
		int first = entNum;
		do
		{
			// take the first entity that can do an attack
			Entity en = bot.game.getEntity(entNum);
			PhysicalOption bestAttack = getBestPhysical(en, bot.game);

			if (bestAttack != null)
			{

				return bestAttack;

			} // End no-attack
			entNum = bot.game.getNextEntityNum(entNum);

		}
		while ((entNum != -1) && (entNum != first));

		// Didn't find any physical attack.
		return null;
	}

	static PhysicalOption getBestPhysical(Entity entity, IGame game)
	{
		// Infantry can't conduct physical attacks.
		if (entity instanceof Infantry)
		{
			return null;
		}

		// if you're charging, it's already declared
		if (entity.isCharging() || entity.isMakingDfa())
		{
			return null;
		}

		PhysicalOption best = null;
		ToHitData odds;
		double breach;
		double breach_a;
		double l_dmg;
		double r_dmg;
		double final_dmg;
		int best_brush = PhysicalOption.NONE;

		// If the attacker is a Mech

		if (entity instanceof Mech)
		{

			l_dmg = 0.0;
			r_dmg = 0.0;
			final_dmg = 0.0;
			breach_a = 0.0;

			// If the attacker is being swarmed

			if (entity.getSwarmAttackerId() != Entity.NONE)
			{

				// Check for left arm punch damage to self
				// the odds of the entity taken damage
				odds = BrushOffAttackAction.toHit(game, entity.getId(), game.getEntity(entity.getSwarmAttackerId()), BrushOffAttackAction.LEFT);
				if (odds.getValue() != TargetRoll.IMPOSSIBLE)
				{

					l_dmg = BrushOffAttackAction.getDamageFor(entity, BrushOffAttackAction.LEFT);
					l_dmg *= 1.0 - Compute.oddsAbove(odds.getValue()) / 100.0;
					breach = punchThroughMod(entity, ToHitData.HIT_PUNCH, ToHitData.SIDE_FRONT, l_dmg, l_dmg);
					if (breach < 1.5)
					{
						best_brush = PhysicalOption.BRUSH_LEFT;
						breach_a = breach;
						final_dmg = BrushOffAttackAction.getDamageFor(entity, BrushOffAttackAction.LEFT);
					}
				}

				// Check for right arm punch damage to self
				odds = BrushOffAttackAction.toHit(game, entity.getId(), game.getEntity(entity.getSwarmAttackerId()), BrushOffAttackAction.RIGHT);
				if (odds.getValue() != TargetRoll.IMPOSSIBLE)
				{

					// If chance of breaching armor is minimal set brush left

					r_dmg = BrushOffAttackAction.getDamageFor(entity, BrushOffAttackAction.RIGHT);
					r_dmg *= 1.0 - Compute.oddsAbove(odds.getValue()) / 100.0;
					breach = punchThroughMod(entity, ToHitData.HIT_PUNCH, ToHitData.SIDE_FRONT, r_dmg, r_dmg);
					if (breach < Math.min(breach_a, 1.5))
					{
						best_brush = PhysicalOption.BRUSH_RIGHT;
						breach_a = breach;
						final_dmg = BrushOffAttackAction.getDamageFor(entity, BrushOffAttackAction.RIGHT);
					}
				}

				// If both arms are capable of punching, check double punch
				// damage

				if ((l_dmg > 0) && (r_dmg > 0))
				{

					// If chance of breaching armor is minimal set double brush

					breach = punchThroughMod(entity, ToHitData.HIT_PUNCH, ToHitData.SIDE_FRONT, l_dmg + r_dmg, (l_dmg + r_dmg) / 2.0);
					if (breach < Math.min(breach_a, 1.5))
					{
						best_brush = PhysicalOption.BRUSH_BOTH;
						breach_a = breach;
						final_dmg = BrushOffAttackAction.getDamageFor(entity, BrushOffAttackAction.LEFT);
						final_dmg += BrushOffAttackAction.getDamageFor(entity, BrushOffAttackAction.RIGHT);
					}
				}

				// Construct and return Physical option
				if (best_brush != PhysicalOption.NONE)
				{
					return new PhysicalOption(entity, game.getEntity(entity.getSwarmAttackerId()), final_dmg, best_brush, null);
				}
			}

			// If the attacker has attached iNarc pods, assign
			// a competing damage value for comparison with other
			// attacks

			if (entity.hasINarcPodsAttached())
			{
				double test_ranking;
				double pod_ranking;
				INarcPod test_pod;
				INarcPod best_pod;
				pod_ranking = 0.0;
				Iterator<INarcPod> pod_list = entity.getINarcPodsAttached();
				best_pod = pod_list.next();
				for (pod_list = entity.getINarcPodsAttached(); pod_list.hasNext();)
				{
					test_ranking = 1.0;
					test_pod = pod_list.next();
					// If pod is homing and attacker has no ECM
					if ((test_pod.getType() == INarcPod.HOMING) && !entity.hasActiveECM())
					{
						// Pod is +1
						test_ranking += 1.0;
					}
					// If pod is ECM and attacker has C3 link
					if ((test_pod.getType() == INarcPod.ECM) && (entity.hasC3() || entity.hasC3i()))
					{
						// Pod is +2
						test_ranking += 2.0;
					}
					// If pod is Nemesis
					if (test_pod.getType() == INarcPod.NEMESIS)
					{
						// Pod is +variable, based on movement
						test_ranking += (entity.getWalkMP() + entity.getJumpMP()) / 2.0;
					}
					// If this pod is best, retain it and its ranking
					if (test_ranking > pod_ranking)
					{
						pod_ranking = test_ranking;
						best_pod = test_pod;
					}
				}
				if (best_pod != null)
				{
					// Check for left arm punch damage to self
					odds = BrushOffAttackAction.toHit(game, entity.getId(), best_pod, BrushOffAttackAction.LEFT);
					if (odds.getValue() != TargetRoll.IMPOSSIBLE)
					{

						l_dmg = BrushOffAttackAction.getDamageFor(entity, BrushOffAttackAction.LEFT);
						l_dmg *= 1.0 - Compute.oddsAbove(odds.getValue()) / 100.0;
						breach = punchThroughMod(entity, ToHitData.HIT_PUNCH, ToHitData.SIDE_FRONT, l_dmg, l_dmg);
						if (breach < 1.5)
						{
							best_brush = PhysicalOption.BRUSH_LEFT;
							breach_a = breach;
							final_dmg = BrushOffAttackAction.getDamageFor(entity, BrushOffAttackAction.LEFT);
						}
					}

					// Check for right arm punch damage to self
					odds = BrushOffAttackAction.toHit(game, entity.getId(), best_pod, BrushOffAttackAction.RIGHT);
					if (odds.getValue() != TargetRoll.IMPOSSIBLE)
					{

						// If chance of breaching armor is minimal set brush
						// left

						r_dmg = BrushOffAttackAction.getDamageFor(entity, BrushOffAttackAction.RIGHT);
						r_dmg *= 1.0 - Compute.oddsAbove(odds.getValue()) / 100.0;
						breach = punchThroughMod(entity, ToHitData.HIT_PUNCH, ToHitData.SIDE_FRONT, r_dmg, r_dmg);
						if (breach < Math.min(breach_a, 1.5))
						{
							best_brush = PhysicalOption.BRUSH_RIGHT;
							breach_a = breach;
							final_dmg = BrushOffAttackAction.getDamageFor(entity, BrushOffAttackAction.RIGHT);
						}
					}

					// If both arms are capable of punching, check double punch
					// damage

					if ((l_dmg > 0) && (r_dmg > 0))
					{

						// If chance of breaching armor is minimal set double
						// brush

						breach = punchThroughMod(entity, ToHitData.HIT_PUNCH, ToHitData.SIDE_FRONT, l_dmg + r_dmg, (l_dmg + r_dmg) / 2.0);
						if (breach < Math.min(breach_a, 1.5))
						{
							best_brush = PhysicalOption.BRUSH_BOTH;
							final_dmg = BrushOffAttackAction.getDamageFor(entity, BrushOffAttackAction.LEFT);
							final_dmg += BrushOffAttackAction.getDamageFor(entity, BrushOffAttackAction.RIGHT);
						}
					}

					// Construct and return Physical option
					if (best_brush != PhysicalOption.NONE)
					{
						return new PhysicalOption(entity, best_pod, final_dmg, best_brush, null);
					}
				}
			}
		}

		for (Enumeration<Entity> e = game.getEntities(); e.hasMoreElements();)
		{
			Entity target = e.nextElement();

			if (target.equals(entity))
			{
				continue;
			}
			if (!target.isEnemyOf(entity))
			{
				continue;
			}
			if (target.getPosition() == null)
			{
				continue;
			}
			if (Compute.effectiveDistance(game, entity, target) > 1)
			{
				continue;
			}

			PhysicalOption one = getBestPhysicalAttack(entity, target, game);
			if (one != null)
			{
				if ((best == null) || (one.expectedDmg > best.expectedDmg))
				{
					best = one;
				}
			}
		}
		if (best == null)
		{
			best = new PhysicalOption(entity);
		}
		return best;
	}

	/**
	 * Gets the best physical attack.
	 *
	 * @param attacker the entity that is perform the attack
	 * @param targetEntity the entity that is being attacked
	 * @param game the game
	 * @return the best physical attack
	 */
	static PhysicalOption getBestPhysicalAttack(Entity attacker, Entity targetEntity, IGame game)
	{
		double bestDmg = 0.0;
		double dmg;
		int damage;
		int target_arc;
		int location_table;
		int bestType = PhysicalOption.NONE;
		Mounted bestClub = null;
		boolean targetConvInfantry = false;

		// Infantry and tanks can't conduct any of these attacks
		if ((attacker instanceof Infantry) || (attacker instanceof Tank))
		{
			return null;
		}

		if ((targetEntity instanceof Infantry) && !(targetEntity instanceof BattleArmor))
		{
			targetConvInfantry = true;
		}

		// Find arc the attack comes in
		target_arc = CEntity.getThreatHitArc(targetEntity.getPosition(), targetEntity.getFacing(), attacker.getPosition());

		// Check for punches
		// If the target is a Mech, must determine if punch lands on the punch,
		// kick, or full table
		if (targetEntity instanceof Mech)
		{
			if (!targetEntity.isProne())
			{
				location_table = ToHitData.HIT_PUNCH;
				if (targetEntity.getElevation() == attacker.getElevation() + 1)
				{
					location_table = ToHitData.HIT_KICK;
				}
			}
			else
			{
				location_table = ToHitData.HIT_NORMAL;
			}
		}
		else
		{
			location_table = ToHitData.HIT_NORMAL;
		}

		ToHitData odds = PunchAttackAction.toHit(game, attacker.getId(), targetEntity, PunchAttackAction.LEFT);
		if (odds.getValue() != TargetRoll.IMPOSSIBLE)
		{
			damage = PunchAttackAction.getDamageFor(attacker, PunchAttackAction.LEFT, targetConvInfantry);
			bestDmg = Compute.oddsAbove(odds.getValue()) / 100.0 * damage;
			// Adjust damage for targets armor
			bestType = PhysicalOption.PUNCH_LEFT;
			bestDmg *= punchThroughMod(targetEntity, location_table, target_arc, bestDmg, bestDmg);
		}

		odds = PunchAttackAction.toHit(game, attacker.getId(), targetEntity, PunchAttackAction.RIGHT);
		if (odds.getValue() != TargetRoll.IMPOSSIBLE)
		{
			damage = PunchAttackAction.getDamageFor(attacker, PunchAttackAction.RIGHT, targetConvInfantry);
			dmg = Compute.oddsAbove(odds.getValue()) / 100.0 * damage;
			// Adjust damage for targets armor
			dmg *= punchThroughMod(targetEntity, location_table, target_arc, dmg, dmg);
			if (dmg > bestDmg)
			{
				bestType = PhysicalOption.PUNCH_RIGHT;
				bestDmg = dmg;
			}
		}

		// Check for a double punch
		odds = PunchAttackAction.toHit(game, attacker.getId(), targetEntity, PunchAttackAction.LEFT);
		ToHitData odds_a = PunchAttackAction.toHit(game, attacker.getId(), targetEntity, PunchAttackAction.RIGHT);
		if ((odds.getValue() != TargetRoll.IMPOSSIBLE) && (odds_a.getValue() != TargetRoll.IMPOSSIBLE))
		{
			damage = PunchAttackAction.getDamageFor(attacker, PunchAttackAction.LEFT, targetConvInfantry);
			dmg = Compute.oddsAbove(odds.getValue()) / 100.0 * damage;
			double dmg_a = Compute.oddsAbove(odds_a.getValue()) / 100.0 * damage;
			dmg += dmg_a;
			dmg *= punchThroughMod(targetEntity, location_table, target_arc, dmg, dmg / 2.0);
			if (dmg > bestDmg)
			{
				bestType = PhysicalOption.PUNCH_BOTH;
				bestDmg = dmg;
			}
		}

		// Check for a kick
		// If the target is a Mech, must determine if it lands on the kick or
		// punch table
		if (targetEntity instanceof Mech)
		{
			location_table = ToHitData.HIT_KICK;
			if (!targetEntity.isProne())
			{
				if (targetEntity.getElevation() == attacker.getElevation() - 1)
				{
					location_table = ToHitData.HIT_PUNCH;
				}
			}
			else
			{
				location_table = ToHitData.HIT_NORMAL;
			}
		}
		else
		{
			location_table = ToHitData.HIT_NORMAL;
		}

		dmg = getExpectedKickDamage(attacker, targetEntity, game, location_table, target_arc, KickAttackAction.LEFT);
		if (dmg > bestDmg)
		{
			bestType = PhysicalOption.KICK_LEFT;
			bestDmg = dmg;
		}

		dmg = getExpectedKickDamage(attacker, targetEntity, game, location_table, target_arc, KickAttackAction.RIGHT);
		if (dmg > bestDmg)
		{
			bestType = PhysicalOption.KICK_RIGHT;
			bestDmg = dmg;
		}

		// Check for mounted club-type weapon or carried improvised club
		for (Mounted club : attacker.getClubs())
		{
			// If the target is a Mech, must determine if it hits full body,
			// punch, or kick table
			if (targetEntity instanceof Mech)
			{
				location_table = ToHitData.HIT_NORMAL;
				if ((targetEntity.getElevation() == attacker.getElevation() - 1) && !targetEntity.isProne())
				{
					location_table = ToHitData.HIT_PUNCH;
				}
				if ((targetEntity.getElevation() == attacker.getElevation() + 1) && !targetEntity.isProne())
				{
					location_table = ToHitData.HIT_KICK;
				}
			}
			else
			{
				location_table = ToHitData.HIT_NORMAL;
			}
			odds = ClubAttackAction.toHit(game, attacker.getId(), targetEntity, club, ToHitData.HIT_NORMAL);
			if (odds.getValue() != TargetRoll.IMPOSSIBLE)
			{
				damage = ClubAttackAction.getDamageFor(attacker, club, targetConvInfantry);
				dmg = Compute.oddsAbove(odds.getValue()) / 100.0 * damage;
				// Adjust damage for targets armor
				dmg *= punchThroughMod(targetEntity, location_table, target_arc, dmg, dmg);
				// Some types of clubs, such as the mace, require a piloting
				// check on a missed attack
				// Calculate self damage in the same manner as a missed kick
				if (dmg > bestDmg)
				{
					bestType = PhysicalOption.USE_CLUB;
					bestDmg = dmg;
					bestClub = club;
				}
			}
		}
		// Check for a push attack
		odds = PushAttackAction.toHit(game, attacker.getId(), targetEntity);
		if (odds.getValue() != TargetRoll.IMPOSSIBLE)
		{
			int elev_diff;
			double breach;
			boolean water_landing = false;
			dmg = 0.0;
			int disp_dir = attacker.getPosition().direction(targetEntity.getPosition());
			Coords disp_c = targetEntity.getPosition().translated(disp_dir);
			// If the displacement hex is a valid one
			if (Compute.isValidDisplacement(game, targetEntity.getId(), targetEntity.getPosition(), disp_c))
			{
				// If the displacement hex is not on the map, credit damage
				// against full target armor
				if (!game.getBoard().contains(disp_c))
				{
					dmg = targetEntity.getTotalArmor() * Compute.oddsAbove(odds.getValue()) / 100.0;
				}
				if (game.getBoard().contains(disp_c))
				{
					// Find the elevation difference
					elev_diff = game.getBoard().getHex(targetEntity.getPosition()).getElevation();
					elev_diff -= game.getBoard().getHex(disp_c).getElevation();
					if (elev_diff < 0)
					{
						elev_diff = 0;
					}
					// Set a flag if the displacement hex has water
					if (game.getBoard().getHex(disp_c).containsTerrain(Terrains.WATER))
					{
						water_landing = true;
					}
					// Get the base damage from target falling, multiplied by
					// the elevation difference
					dmg = calculateFallingDamage(Compute.oddsAbove(odds.getValue()) / 100.0, targetEntity) * (1.0 + elev_diff);
					// Calculate breach factor of falling damage
					breach = punchThroughMod(targetEntity, ToHitData.HIT_NORMAL, ToHitData.SIDE_FRONT, dmg, Math.min(dmg, 5.0));
					// If breach factor is > 1 and displacement hex has water
					if ((breach > 1) && water_landing)
					{
						breach *= 2.0;
					}
					// Modify damage to reflect how bad it is for target to be
					// prone
					if (targetEntity.getWalkMP() > 0)
					{
						dmg = dmg * Math.sqrt(1.0 / targetEntity.getWalkMP() + targetEntity.getJumpMP());
					}
					else
					{
						dmg *= Math.max(1.0, Math.sqrt(targetEntity.getJumpMP()));
					}
					// Modify damage by breach factor
					dmg *= breach;
				}
			}
			// If the displacement hex is not valid
			if (!Compute.isValidDisplacement(game, targetEntity.getId(), targetEntity.getPosition(), disp_c))
			{
				// Set a flag if the displacement hex has water
				if (game.getBoard().getHex(targetEntity.getPosition()).containsTerrain(Terrains.WATER))
				{
					water_landing = true;
				}
				// Get falling in place
				dmg = calculateFallingDamage(Compute.oddsAbove(odds.getValue()) / 100.0, targetEntity);
				// Calculate breach factor of falling damage
				breach = punchThroughMod(targetEntity, ToHitData.HIT_NORMAL, ToHitData.SIDE_FRONT, dmg, Math.min(dmg, 5.0));
				// If breach factor is > 1 and target hex is in water
				if ((breach > 1) && water_landing)
				{
					breach *= 2.0;
				}
				// Modify damage to reflect how bad it is for target to be prone
				if (targetEntity.getWalkMP() > 0)
				{
					dmg = dmg * Math.sqrt(1.0 / targetEntity.getWalkMP() + targetEntity.getJumpMP());
				}
				else
				{
					dmg = dmg * Math.max(1.0, Math.sqrt(targetEntity.getJumpMP()));
				}
				// Modify damage by breach factor
				dmg *= breach;
			}
			// If damage is better than best damage
			if (dmg > bestDmg)
			{
				bestType = PhysicalOption.PUSH_ATTACK;
				bestDmg = dmg;
			}
		}

		// Conventional infantry in the open suffer double damage.
		if ((targetEntity instanceof Infantry) && !(targetEntity instanceof BattleArmor))
		{
			IHex e_hex = game.getBoard().getHex(targetEntity.getPosition());
			if (!e_hex.containsTerrain(Terrains.WOODS) && !e_hex.containsTerrain(Terrains.BUILDING))
			{
				bestDmg *= 2.0;
			}
		}

		if (bestDmg > 0)
		{
			return new PhysicalOption(attacker, targetEntity, bestDmg, bestType, bestClub);
		}
		return null;
	}

	/**
	 * Calculates the Falling damage after a successful To-Hit.
	 * 
	 * @param odds
	 * @param ent
	 *            The entity that is falling
	 * @return
	 */
	private static double calculateFallingDamage(double odds, Entity ent)
	{
		double dmg = odds;
		dmg *= 1.0 - Compute.oddsAbove(ent.getBasePilotingRoll().getValue()) / 100.0;
		dmg *= ent.getWeight() * 0.1;
		return dmg;
	}

	private static double getExpectedKickDamage(Entity from, Entity to, IGame game, int locTable, int arc, int action)
	{
		double self_damage;
		double dmg;
		double coll_damage = 0.0;
		int damage;
		boolean targetConvInfantry = false;
		ToHitData odds = KickAttackAction.toHit(game, from.getId(), to, action);
		if (odds.getValue() == TargetRoll.IMPOSSIBLE)
		{
			return 0.0;
		}

		if ((to instanceof Infantry) && !(to instanceof BattleArmor))
		{
			targetConvInfantry = true;
		}

		// Calculate collateral damage, due to possible target fall
		if (to instanceof Mech)
		{
			coll_damage = calculateFallingDamage(Compute.oddsAbove(odds.getValue()) / 100.0, to);
		}

		damage = KickAttackAction.getDamageFor(from, action, targetConvInfantry);
		dmg = Compute.oddsAbove(odds.getValue()) / 100.0 * damage;
		// Adjust damage for targets armor
		dmg *= punchThroughMod(to, locTable, arc, dmg, dmg);
		// Calculate self damage, due to possible fall from missing a kick
		self_damage = calculateFallingDamage(1.0 - Compute.oddsAbove(odds.getValue()) / 100.0, from);
		if (from.getWalkMP() > 0)
		{
			self_damage = self_damage * Math.sqrt(1.0 / from.getWalkMP() + from.getJumpMP());
		}
		else
		{
			self_damage = self_damage * Math.sqrt(from.getJumpMP());
		}
		// Add together damage values for comparison
		dmg = dmg + coll_damage - self_damage;
		return dmg;
	}

	/**
	 * This checks to see if the damage will punch through armor anywhere in the
	 * attacked arc. damage argument is divided into hits, using the group
	 * argument (ie, group = 5.0 for LRM). Each hit of group damage is checked
	 * against each location; if it penetrates increase the multiplier to
	 * reflect potential for additional damage Multiple passes are made with
	 * each hit being multiples of group damage to reflect shot grouping; as
	 * each pass is made the increase to the multiplier is lowered due to the
	 * lower chance of hitting the same location
	 */
	private static double punchThroughMod(Entity target, int hitTable, int hitSide, double damage, double group)
	{

		int[] armor_values = new int[8];
		int max_index = 1;
		armor_values[0] = 0;

		// Set the final multiplier as 1.0 (no chance of penetrating armor)
		double final_multiplier = 1.0;

		// Set the base multiplier as 0.5 (good bonus for penetrating with a
		// single hit)
		double base_multiplier = 0.5;

		if ((damage <= 0.0) || (group <= 0.0))
		{
			return final_multiplier;
		}

		// If the target is a Mech
		if (target instanceof Mech)
		{
			// Create vector of body locations with targets current armor values
			// Use hit table and direction to determine locations that are hit
			if (hitTable == ToHitData.HIT_NORMAL)
			{
				max_index = 7;
				armor_values[0] = target.getArmor(Mech.LOC_HEAD, false);
				if (hitSide != ToHitData.SIDE_FRONT)
				{
					armor_values[1] = target.getArmor(Mech.LOC_CT, true);
				}
				else
				{
					armor_values[1] = target.getArmor(Mech.LOC_CT, false);
				}
				if (hitSide != ToHitData.SIDE_FRONT)
				{
					armor_values[2] = target.getArmor(Mech.LOC_RT, true);
				}
				else
				{
					armor_values[2] = target.getArmor(Mech.LOC_RT, false);
				}
				if (hitSide != ToHitData.SIDE_FRONT)
				{
					armor_values[3] = target.getArmor(Mech.LOC_LT, true);
				}
				else
				{
					armor_values[3] = target.getArmor(Mech.LOC_LT, false);
				}
				armor_values[4] = target.getArmor(Mech.LOC_RARM, false);
				armor_values[5] = target.getArmor(Mech.LOC_LARM, false);
				armor_values[6] = target.getArmor(Mech.LOC_RLEG, false);
				armor_values[7] = target.getArmor(Mech.LOC_RLEG, false);
			}
			if (hitTable == ToHitData.HIT_PUNCH)
			{
				armor_values[0] = target.getArmor(Mech.LOC_HEAD, false);
				if (hitSide == ToHitData.SIDE_RIGHT)
				{
					max_index = 3;
					armor_values[1] = target.getArmor(Mech.LOC_CT, false);
					armor_values[2] = target.getArmor(Mech.LOC_RT, false);
					armor_values[3] = target.getArmor(Mech.LOC_RARM, false);
				}
				if (hitSide == ToHitData.SIDE_LEFT)
				{
					max_index = 3;
					armor_values[1] = target.getArmor(Mech.LOC_CT, false);
					armor_values[2] = target.getArmor(Mech.LOC_LT, false);
					armor_values[3] = target.getArmor(Mech.LOC_LARM, false);
				}
				if (hitSide == ToHitData.SIDE_FRONT)
				{
					max_index = 5;
					armor_values[1] = target.getArmor(Mech.LOC_CT, false);
					armor_values[2] = target.getArmor(Mech.LOC_RT, false);
					armor_values[3] = target.getArmor(Mech.LOC_LT, false);
					armor_values[4] = target.getArmor(Mech.LOC_RARM, false);
					armor_values[5] = target.getArmor(Mech.LOC_LARM, false);
				}
				if (hitSide == ToHitData.SIDE_REAR)
				{
					max_index = 5;
					armor_values[1] = target.getArmor(Mech.LOC_CT, true);
					armor_values[2] = target.getArmor(Mech.LOC_RT, true);
					armor_values[3] = target.getArmor(Mech.LOC_LT, true);
					armor_values[4] = target.getArmor(Mech.LOC_RARM, false);
					armor_values[5] = target.getArmor(Mech.LOC_LARM, false);
				}
			}
			if (hitTable == ToHitData.HIT_KICK)
			{
				max_index = -1;
				if ((hitSide == ToHitData.SIDE_FRONT) || (hitSide == ToHitData.SIDE_REAR) || (hitSide == ToHitData.SIDE_RIGHT))
				{
					max_index++;
					armor_values[max_index] = target.getArmor(Mech.LOC_RLEG, false);
				}
				if ((hitSide == ToHitData.SIDE_FRONT) || (hitSide == ToHitData.SIDE_REAR) || (hitSide == ToHitData.SIDE_LEFT))
				{
					max_index++;
					armor_values[max_index] = target.getArmor(Mech.LOC_LLEG, false);
				}
			}
		}
		// If the target is a ProtoMech
		if (target instanceof Protomech)
		{
			max_index = 6;
			// Create vector of body locations with targets current armor values
			// Create two high-armor dummy locations to represent the 'near
			// miss' hit locations
			armor_values[0] = target.getArmor(Protomech.LOC_TORSO, false);
			armor_values[1] = target.getArmor(Protomech.LOC_LEG, false);
			armor_values[2] = target.getArmor(Protomech.LOC_RARM, false);
			armor_values[3] = target.getArmor(Protomech.LOC_LARM, false);
			armor_values[4] = target.getArmor(Protomech.LOC_HEAD, false);
			armor_values[5] = 100;
			armor_values[6] = 100;
			if (((Protomech) target).hasMainGun())
			{
				max_index++;
				armor_values[max_index] = target.getArmor(Protomech.LOC_MAINGUN, false);
			}
		}
		// If the target is a vehicle
		if (target instanceof Tank)
		{
			// Create vector of armor locations
			max_index = 0;
			switch (hitSide)
			{
				case ToHitData.SIDE_FRONT:
					armor_values[0] = target.getArmor(Tank.LOC_FRONT);
					break;
				case ToHitData.SIDE_RIGHT:
					armor_values[0] = target.getArmor(Tank.LOC_RIGHT);
					break;
				case ToHitData.SIDE_LEFT:
					armor_values[0] = target.getArmor(Tank.LOC_LEFT);
					break;
				case ToHitData.SIDE_REAR:
					armor_values[0] = target.getArmor(Tank.LOC_REAR);
					break;
			}
			if (!((Tank) target).hasNoTurret())
			{
				max_index++;
				armor_values[max_index] = target.getArmor(Tank.LOC_TURRET);
			}
		}
		// If the target is Battle Armor
		if (target instanceof BattleArmor)
		{
			// Create vector of armor of surviving troopers
			max_index = -1;
			for (int i = 1; i < ((BattleArmor) target).getShootingStrength(); i++)
			{
				if (target.getArmor(i) >= 0)
				{
					max_index++;
					armor_values[max_index] = target.getArmor(i);
				}
			}
		}
		// If the target is conventional infantry
		if ((target instanceof Infantry) && !(target instanceof BattleArmor))
		{
			// Create a single element vector with total number of troopers
			max_index = 0;
			armor_values[0] = ((Infantry) target).getShootingStrength();
		}

		double hit_total = 0;
		// While hit damage is less than total damage applied, increment by
		// group value
		while (hit_total <= damage)
		{
			hit_total += group;
			for (int i = 0; i <= max_index; i++)
			{
				// If hit damage can penetrate location
				if (hit_total > armor_values[i])
				{
					final_multiplier += base_multiplier;
				}
			}
			base_multiplier /= 2.0;
		}

		// Return final multiplier

		return final_multiplier;

	}
}
